package p.ithorns.sample.module;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.web.servlet.RegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import p.ithorns.sample.ModulePath;

import javax.servlet.Servlet;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Set;

/**
 * ServletRegistrationRegistrar
 * <p>
 * ServletRegistrationBean的注入参考{@link RegistrationBean#onStartup}
 *
 * @author ithorns
 * @version 1.0
 * @date 2021/10/26 13:56
 */
public class ModuleAnnotationRegistrar implements ImportBeanDefinitionRegistrar {

    private final Logger log = LoggerFactory.getLogger(ModuleAnnotationRegistrar.class);

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        log.info("Start to load ServletRegistrationRegistrar...");

        String basePackage = "p.ithorns.sample";
        ModuleAnnotationScanner scanner = new ModuleAnnotationScanner(registry);
        Set<BeanDefinition> configs = scanner.scan(basePackage);

        for (BeanDefinition beanDef : configs) {
            if (beanDef instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition annoDef = (AnnotatedBeanDefinition) beanDef;
                AnnotationMetadata annoMeta = annoDef.getMetadata();
                Map<String, Object> attributes = annoMeta.getAnnotationAttributes(ModulePath.class.getName());
                String moduleName = (String) attributes.get("name");
                String modulePath = (String) attributes.get("path");
                String alias = (String) attributes.get("alias");
                registerServletBean(registry, moduleName, modulePath);
                log.info("==> 注入模块: {}", alias);
            }
        }
    }

    /**
     * 注册ServletRegistrationBean到Spring容器
     *
     * @param registry -
     * @param name     -
     * @param path     -
     * @see ServletRegistrationBean#ServletRegistrationBean(Servlet, String...)
     */
    private void registerServletBean(BeanDefinitionRegistry registry, String name, String path) {
        // 如果需要单独的DispatcherServlet, 则需要先注入
        // registerServlet(registry, moduleName);

        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(ServletRegistrationBean.class)
                // 构造参数-servlet: 这里直接使用{@link DispatcherServlet}的名称引用来实现后置注入
                .addConstructorArgReference("dispatcherServlet")
                // 构造参数urlMappings
                // 从{@link UrlPathHelper.Servlet4Delegate#skipServletPathDetermination}方法可知，
                // 当MappingMatch不为PATH时，ServletRegistrationBean注册的后缀就必须加"/*"才能正常匹配
                .addConstructorArgValue(path + "/*")
                // Servlet名称
                .addPropertyValue("name", name + "Servlet")
                // MultipartConfig，这个通过名字注入对象的？？？ 没搞清楚
                .addPropertyValue("multipartConfig", "multipartConfig")
                .getBeanDefinition();
        registry.registerBeanDefinition(name + "Registration", beanDefinition);
    }

    /**
     * 手动注入新的DispatcherServlet
     * 名字不能和默认的DispatcherServlet名称一样
     *
     * @param registry   -
     * @param moduleName -
     */
    private void registerServlet(BeanDefinitionRegistry registry, String moduleName) {
        String servletName = moduleName + "Servlet";
        BeanDefinitionBuilder dispatcherBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(DispatcherServlet.class)
                // WebApplicationContext属性必须指定：没有容器还怎么玩！
                // 这里直接使用现成的，当然也可以直接new新的AnnotationConfigWebApplicationContext给Servlet,但不建议这么搞
                .addPropertyValue("contextClass", AnnotationConfigWebApplicationContext.class);
        registry.registerBeanDefinition(servletName, dispatcherBuilder.getBeanDefinition());
    }

    private ModulePath getBean(String beanClassName) {
        try {
            Class<?> aClass = Class.forName(beanClassName);
            Constructor<?> constructor = aClass.getDeclaredConstructor(null);
            Object bean = constructor.newInstance(null);
            return (ModulePath) bean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}